#ifndef __QyMath__
#define __QyMath__

#include <math.h>
#include <QDataStream>
#include <QDebug>
#include <QLineF>
#include <QList>
#include <QPointF>
#include <QtGlobal>
#include <QtOpenGL>
#include <QyDebug.h>


//! Пространство имен для матиматики
namespace QyMath {

typedef qreal RealVec[3]; //!< Вектор (устарел, оставлен для совместимости со старым функцианалом)

/*! \brief Точка в пространстве
*/
class Point {
public:
	Point() /*: x(p[0]), y(p[1]), z(p[2]) */{}
	explicit Point( const qreal & cx, const qreal & cy = 0.0, const qreal & cz = 0.0 )
		/*: x(p[0]), y(p[1]), z(p[2])*/ { p[0] = cx; p[1] = cy; p[2] = cz; }
	explicit Point( qreal * cp ) /*: x(p[0]), y(p[1]), z(p[2])*/
		{ memcpy( p, cp, sizeof(qreal) * 3 ); }

	qreal p[3]; //!< Массив содержащий координаты x, y, z
	// qreal & x, & y, & z;

};


/*! \brief ЛУч в пространстве
*/
struct Beam {
	Point p1,  //!< первая точка на луче
        p2;  //!< вторая точка на луче

	// inline Point & operator [] ( int i ) { return (i) ? p2 : p1; }

};

static const qreal pi2 = M_PI * 2.0, //!< 2 пи
	oneRad = 57.29577951308, //!< Градусов в радиане
	pi = M_PI;  //!< пи

//! Пересчитать градусы в радианы
inline qreal radian( qreal angle ) {

	return angle / oneRad;
}

//! Пересчитать радианы в градусы
inline qreal degress( qreal radian ) {

	return radian * oneRad;
}

//! Нормировать значение угла так, что-бы оно лежало в промежутке от 0 до 360, значение в градусах
inline qreal normalizeAngle( qreal angle ) {

	qreal r = angle;

	while ( r < 0 )
		r += 360;

	while ( r >= 360 )
		r -= 360;

	return r;
}

//! Проверить значение на входимость в диапазон, вернет значение входящие в деапазон
inline qreal normalizeAngle( qreal angle, qreal min, qreal max ) {

	qreal r = angle;

	if ( r > max )
		r = max;

	if ( r < min )
		r = min;

	return r;
}

//! Установить значения для массива
inline void setVec( qreal * vec, qreal val=0.0, int len = 3 ) {

	for ( int i=0; i<len; i++ )
		vec[i] = val;

}

//! Сгенерировать координаты луча под курсором, x, y - позиция курсора.
inline QyMath::Beam generateBeam( int x, int y ) {

	qint32 viewport[4];
	qreal projection[16], modelview[16], vx, vy;

	glGetIntegerv( GL_VIEWPORT, viewport );
	glGetDoublev( GL_PROJECTION_MATRIX, projection );
	glGetDoublev( GL_MODELVIEW_MATRIX, modelview );

	vx = x;
	vy = viewport[3] - y - 1;

	Beam beam;

	gluUnProject( vx, vy, 0.0, modelview, projection, viewport, &beam.p1.p[0], &beam.p1.p[1], &beam.p1.p[2] );
	gluUnProject( vx, vy, 1.0, modelview, projection, viewport, &beam.p2.p[0], &beam.p2.p[1], &beam.p2.p[2] );

	// qDebug() << "beam 0" << beam.p1.x << beam.p1.y << beam.p1.z;
	// qDebug() << "beam 1" << beam.p2.x << beam.p2.y << beam.p2.z;

	return beam;
}

//! Посчитать определитель матрицы 3х3
inline qreal calculateDeterminant_3x3( RealVec * det ) {

	return
		det[0][0] * det[1][1] * det[2][2] +
		det[0][1] * det[1][2] * det[2][0] +
		det[0][2] * det[1][0] * det[2][1] -
		det[0][0] * det[1][2] * det[2][1] -
		det[0][1] * det[1][0] * det[2][2] -
		det[0][2] * det[1][1] * det[2][0];
}

//! Посчитать определитель матрицы 2х2
inline qreal calculateDeterminant_2x2( RealVec * det ) {

	return det[0][0] * det[1][1] - det[0][1] * det[1][0];
}

//! Сгенерировать коэффиценты плоскости по 3м точкам
inline void generatePlane( qreal * plane, RealVec * points ) {

	qreal determinantX [3][3] = {

		{ points[0][1], points[0][2], 1.0 },
		{ points[1][1], points[1][2], 1.0 },
		{ points[2][1], points[2][2], 1.0 }

	};

	qreal determinantY [3][3] = {

		{ points[0][2], points[0][0], 1.0 },
		{ points[1][2], points[1][0], 1.0 },
		{ points[2][2], points[2][0], 1.0 }

	};

	qreal determinantZ [3][3] = {

		{ points[0][0], points[0][1], 1.0 },
		{ points[1][0], points[1][1], 1.0 },
		{ points[2][0], points[2][1], 1.0 }

	};

	qreal mainDeterminant [3][3] = {

		{ points[0][0], points[0][1], points[0][2] },
		{ points[1][0], points[1][1], points[1][2] },
		{ points[2][0], points[2][1], points[2][2] }

	};

	plane[0] = calculateDeterminant_3x3(determinantX);
	plane[1] = calculateDeterminant_3x3(determinantY);
	plane[2] = calculateDeterminant_3x3(determinantZ);
	plane[3] = calculateDeterminant_3x3(mainDeterminant);

}

//! Сгенерировать коэффиценты плоскости по 3м точкам
inline void generatePlane( qreal * plane, qreal * point1, qreal * point2, qreal * point3 ) {

	qreal determinantX[3][3] = {
		{ point1[1], point1[2], 1.0 },
		{ point2[1], point2[2], 1.0 },
		{ point3[1], point3[2], 1.0 },
	};

	qreal determinantY[3][3] = {
		{ point1[2], point1[0], 1.0 },
		{ point2[2], point2[0], 1.0 },
		{ point3[2], point3[0], 1.0 },
	};

	qreal determinantZ[3][3] = {
		{ point1[0], point1[1], 1.0 },
		{ point2[0], point2[1], 1.0 },
		{ point3[0], point3[1], 1.0 },
	};

	qreal mainDeterminant[3][3] = {
		{ point1[0], point1[1], point1[2] },
		{ point2[0], point2[1], point2[2] },
		{ point3[0], point3[1], point3[2] }
	};

	plane[0] = calculateDeterminant_3x3(determinantX);
	plane[1] = calculateDeterminant_3x3(determinantY);
	plane[2] = calculateDeterminant_3x3(determinantZ);
	plane[3] = calculateDeterminant_3x3(mainDeterminant);

}

//! Посчитать z координату на заданной 3мя точками плоскости в заданной точке
inline qreal getZValueToPlaneInPoint( qreal * point, RealVec * points ) {

	qreal plane[4];

	generatePlane( plane, points );

	return ( plane[3] - point[0] * plane[0] - point[1] * plane[1] ) / plane[2];
}

//! Найти точку пересечения плоскости и луча
inline Point getPointOfPlane( const Beam & beam, qreal * plane ) {

	qreal vec2[3] = {
		beam.p2.p[0] - beam.p1.p[0],
		beam.p2.p[1] - beam.p1.p[1],
		beam.p2.p[2] - beam.p1.p[2]
	};

	qreal length =
		( plane[0] * beam.p1.p[0] + plane[1] * beam.p1.p[1] + plane[2] * beam.p1.p[2] - plane[3] ) /
		( plane[0] * vec2[0] + plane[1] * vec2[1] + plane[2] * vec2[2] );

	// QyDbgLocation();
	// QyDbgValue(length);
	// QyDbgValue(plane[0]);
	// QyDbgValue(plane[1]);
	// QyDbgValue(plane[2]);
	// QyDbgValue(plane[3]);
	// QyDbgValue(vec2[0]);
	// QyDbgValue(vec2[1]);
	// QyDbgValue(vec2[2]);

	return Point(
		vec2[0] * length - beam.p1.p[0],
		vec2[1] * length - beam.p1.p[1],
		vec2[2] * length - beam.p1.p[2]
	);
}

//! Расстояние между точками
inline qreal distanse( qreal * point1, qreal * point2 ) {

	qreal cat [3] = {
		point1[0] - point2[0], point1[1] - point2[1], point1[2] - point2[2]
	};

	return sqrt( cat[0] * cat[0] + cat[1] * cat[1] + cat[2] * cat[2] );
}

//! Перевернуть значения состовляющих цвета (т.е. aa11cc превратится в 55ee33)
inline QColor invertColor( const QColor & color ) {

	return QColor( 255 - color.red(), 255 - color.green(), 255 - color.blue() );
}

/*! \brief Вектор
Вектор в данном случае рассматривается и как классический вектор (отрезок начало которого в начале
координат, а конец в заданной точке) и как точка, по этому в описании некоторых методов применяются
оба термина.
*/
class Vector : public Point {
public:
    //! Оси
	enum Axis {
		X, //!< Ось X
        Y, //!< Ось Y
        Z //!< Ось Z
	};

	Vector( const Point & point ) : Point(point) {}

	Vector( const qreal * vec ) : Point() {

		memcpy( p, vec, sizeof(qreal) * 3 );

	}

	Vector( const QPointF & point, qreal z=0.0 ) : Point() {

		p[X] = point.x();
		p[Y] = point.y();
		p[Z] = z;

	}

	Vector( const QPoint & point, qreal z=0.0 ) : Point() {

		p[X] = (qreal) point.x();
		p[Y] = (qreal) point.y();
		p[Z] = z;

	}

	Vector( qreal x=0.0, qreal y=0.0, qreal z=0.0 ) : Point() {

		p[0] = x;
		p[1] = y;
		p[2] = z;

	}

	inline qreal x() const {

		return p[X];
	}

	inline qreal y() const {

		return p[Y];
	}

	inline qreal z() const {

		return p[Z];
	}

	inline qreal & operator [] ( int axis ) {

		return p[axis];
	}

	inline Vector & operator = ( qreal * vec ) {

		for ( int i=0; i<3; i++ )
			p[i] = vec[i];

		return *this;
	}

	inline Vector & operator = ( qreal f ) {

		for ( int i=0; i<3; i++ )
			p[i] = f;

		return *this;
	}

	inline Vector operator + ( qreal f ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r.p[i] = p[i] + f;

		return r;
	}

	inline Vector operator - ( qreal f ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r.p[i] = p[i] - f;

		return r;
	}

	inline Vector operator * ( qreal f ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r.p[i] = p[i] * f;

		return r;
	}

	inline Vector operator / ( qreal f ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r.p[i] = p[i] / f;

		return r;
	}

	inline Vector & operator += ( qreal f ) {

		for ( int i=0; i<3; i++ )
			p[i] += f;

		return *this;
	}

	inline Vector & operator -= ( qreal f ) {

		for ( int i=0; i<3; i++ )
			p[i] -= f;

		return *this;
	}

	inline Vector & operator *= ( qreal f ) {

		for ( int i=0; i<3; i++ )
			p[i] *= f;

		return *this;
	}

	inline Vector & operator /= ( qreal f ) {

		for ( int i=0; i<3; i++ )
			p[i] /= f;

		return *this;
	}

	inline Vector & operator = ( const Vector & v ) {

		for ( int i=0; i<3; i++ )
			p[i] = v.p[i];

		return *this;
	}

	inline Vector operator + ( const Vector & v ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r[i] = p[i] + v.p[i];

		return r;
	}

	inline Vector operator - ( const Vector & v ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r[i] = p[i] - v.p[i];

		return r;
	}

	inline Vector operator * ( const Vector & v ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r[i] = p[i] * v.p[i];

		return r;
	}

	inline Vector operator / ( const Vector & v ) const {

		Vector r;

		for ( int i=0; i<3; i++ )
			r[i] = p[i] / v.p[i];

		return r;
	}

	inline Vector & operator += ( Vector v ) {

		for ( int i=0; i<3; i++ )
			p[i] += v.p[i];

		return *this;
	}

	inline Vector & operator -= ( Vector v ) {

		for ( int i=0; i<3; i++ )
			p[i] -= v.p[i];

		return *this;
	}

	inline Vector & operator *= ( Vector v ) {

		for ( int i=0; i<3; i++ )
			p[i] *= v.p[i];

		return *this;
	}

	inline Vector & operator /= ( Vector v ) {

		for ( int i=0; i<3; i++ )
			p[i] /= v.p[i];

		return *this;
	}

	inline bool operator == ( qreal f ) const {

		for ( int i=0; i<3; i++ ) {

			if ( p[i] != f )
				return false;

		}

		return true;
	}

	inline bool operator != ( qreal f ) const {

		for ( int i=0; i<3; i++ ) {

			if ( p[i] != f )
				return true;

		}

		return false;
	}

	inline bool operator == ( const Vector & v ) const {

		for ( int i=0; i<3; i++ ) {

			if ( p[i] != v.p[i] )
				return false;

		}

		return true;
	}

	inline bool operator != ( const Vector & v ) const {

		for ( int i=0; i<3; i++ ) {
			if ( p[i] != v.p[i] )
				return true;
		}

		return false;
	}

	inline bool operator == ( const qreal * vec ) const {

		for ( int i=0; i<3; i++ ) {

			if ( p[i] != vec[i] )
				return false;

		}

		return true;
	}

	inline bool operator != ( const qreal * vec ) const {

		for ( int i=0; i<3; i++ ) {
			if ( p[i] != vec[i] )
				return true;
		}

		return false;
	}

    //! Установить x, y, z
	inline void setup( qreal i, qreal j, qreal k ) {

		p[0] = i;
		p[1] = j;
		p[2] = k;

	}

    //! Повернуть вектор на угол(в градусах), по заданной оси
	inline Vector & rotate( int axis, qreal angle ) {

		if ( angle == 0.0 )
			return *this;

		qreal radian = QyMath::radian(-angle);
		qreal dc_sin = sin(radian),
			dc_cos = cos(radian),
			x = p[0],
			y = p[1],
			z = p[2];

		switch (axis) {

		case X://x
			y = p[1] * dc_cos - p[2] * dc_sin;
			z = p[1] * dc_sin + p[2] * dc_cos;
			break;

		case Y://y
			x = p[0] * dc_cos + p[2] * dc_sin;
			z = -p[0] * dc_sin + p[2] * dc_cos;
			break;

		case Z://z
			x = p[0] * dc_cos - p[1] * dc_sin;
			y = p[0] * dc_sin + p[1] * dc_cos;
			break;

		default:
			return *this;

		}

		p[0] = x;
		p[1] = y;
		p[2] = z;

		return *this;
	}

    //! Привести к единичной длинне
	inline void normalize() {

		*this /= length() / 1.0;

	}

    //! Получить нормализованный вектор
	inline QyMath::Vector normalized() {

		QyMath::Vector vec(*this);

		vec.normalize();
		return  vec;
	}

    //! Длинна вектора
	inline qreal length() const {

		return sqrt( p[0] * p[0] + p[1] * p[1] + p[2] * p[2] );
	}

    //! Расстояние между точками
	inline qreal distance( const Vector & v ) const {

		Vector vlegs( legs(v) );

		return sqrt( vlegs[0] * vlegs[0] + vlegs[1] * vlegs[1] + vlegs[2] * vlegs[2] );
	}

	/*inline qreal distance( const QPointF & p ) const {

		QyVecror v(p);
		Vector vlegs( legs(v) );

		return sqrt( vlegs[0] * vlegs[0] + vlegs[1] * vlegs[1] + vlegs[2] * vlegs[2] );
	}*/

    //! Скалярное произведение
	inline qreal dotProduct( const Vector & v ) const {

	   return p[0] * v.p[0] + p[1] * v.p[1] + p[2] * v.p[2];
	}

    //! Катеты прямоугольного треугольника, образованного 2мя точками
	inline Vector legs( const Vector & v ) const {

		return Vector( v.p[0] - p[0], v.p[1] - p[1], v.p[2] - p[2] );
	}

    //! направление на точку (угол в радианах)
	inline qreal zDirectionR( const Vector & v ) const {

		qreal hypotenuse = distance(v);

		if ( hypotenuse == 0 )
			return 0.0;

		qreal angle = ( v.p[0] - p[0] ) / hypotenuse;
		angle = asin(angle);

		if ( ( v.p[1] - p[1] ) < 0 )
			angle = M_PI - angle;

		while ( angle < 0 )
			angle += QyMath::pi2;

		while ( angle >= QyMath::pi2 )
			angle -= QyMath::pi2;

		return angle;
	}

    //! направление на точку (угол в градусах)
	inline qreal zDirection( const Vector & v ) const {

		return QyMath::degress( zDirectionR(v) );
	}

    //! направление на точку (угол в градусах)
	inline qreal zDirection( const QPointF & p ) const {

		return QyMath::degress( zDirectionR( Vector(p) ) );
	}

    //! Векторное произведение
	inline Vector crossProduct( const Vector & v ) const {

		return Vector(
			( p[1] * v.p[2] ) - ( p[2] * v.p[1] ),
			( p[2] * v.p[0] ) - ( p[0] * v.p[2] ),
			( p[0] * v.p[1] ) - ( p[1] * v.p[0] )
		);
	}

    //! Переместить точку на заданное расстояние, в заданном направлении
	inline Vector & move( qreal distance, qreal angle ) {

		Vector & vec = *this;

		vec = move( vec, distance, angle );

		return vec;
	}

    //! Преобразовать в QPointF
	inline QPointF toPointF() const {

		return QPointF( p[0], p[1] );
	}

    //! Переместить точку на заданное расстояние, в заданном направлении
	static Vector move( const Vector & v, qreal distance, qreal angle ) {

		qreal redian = QyMath::radian(angle);
		qreal x = sin(redian) * distance;
		qreal y = cos(redian) * distance;

		return Vector( v.x() + x, v.y() + y, v.z() );
	}

    //! Переместить точку на заданное расстояние, в заданном направлении
	static Vector move( qreal x, qreal y, qreal distance, qreal angle ) {

		qreal redian = QyMath::radian(angle);

		return Vector( sin(redian) * distance + x, cos(redian) * distance + y );
	}

};

//! Направление от точки на точку
inline qreal zDirection( const QPointF & p1, const QPointF & p2 ) {

	return Vector(p1).zDirection(p2);
}

//! Направление от точки на точку
inline qreal zDirection( const QPointF & p1, const Vector & v2 ) {

	return Vector(p1).zDirection(v2);
}

//! Расстояние от точки до точки
inline qreal distance( const QPointF & p1, const QPointF & p2 ) {

	return QLineF( p1, p2 ).length();
}

//! Расстояние от точки до точки
inline qreal distance( const QPointF & p1, const Vector & v2 ) {

	return v2.distance( Vector(p1) );
}

inline QDataStream & operator << ( QDataStream & ds, const Vector & vector ) {

	ds << vector.p[0];
	ds << vector.p[1];
	ds << vector.p[2];

	return ds;
}

inline QDataStream & operator >> ( QDataStream & ds, Vector & vector ) {

	ds >> vector.p[0];
	ds >> vector.p[1];
	ds >> vector.p[2];

	return ds;
}

typedef QList<Vector> QyVectorList; //!< Список векторов

};

#endif/*__QyMath__*/
